Skip to content

v3: one OpenTelemetry setup, transport 0.29+, generic error shaping - #157

Merged
cuzzlor merged 13 commits into
mainfrom
feat/v3
Oct 17, 2026
Merged

cuzzlor merged 13 commits into
mainfrom
feat/v3

Conversation

@cuzzlor

@cuzzlor cuzzlor commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #156.

v3 has one OpenTelemetry setup. WinstonInstrumentation runs beside the other instrumentations and adds the transport, and createLogger({ otel: true }) shapes what that transport receives. Only @opentelemetry/winston-transport 0.29 and later is supported. The README opens with a quick start that shows the whole setup.

Merging publishes 3.0.0 as latest. Squash-merge, because the intermediate commits do not each pass.

Breaking changes

  • flatten, flattenReplacer and mapAuditLevelForOtel are removed from createLogger. They are type errors, and createLogger throws if one is passed, so options built from config cannot fall back to nested output without a warning.
  • otel shapes only the OpenTelemetry transport. 2.x flatten flattened every transport, the console included.
  • otelAttributesFormat always maps audit to info severity and writes logLevel on every record.
  • @opentelemetry/winston-transport is an optional peer dependency at >=0.29. npm 7 and later refuse to install it beside an older transport.
  • A Date in OpenTelemetry output is written unquoted.

What changed

Area Change
createLogger({ otel }) Gives otelAttributesFormat to each OpenTelemetryTransportV3 on the logger that has no format of its own: the transport WinstonInstrumentation adds, and any passed in transports. The transport is matched by class name, because this library does not depend on it.
Audit guard Removed. The format runs after the transport's level filter, so a logger or transport at level: 'audit' keeps audit records.
otelAttributesFormat Always maps audit to info severity and writes logLevel. A logger with no audit level has nothing to rewrite, and App Insights needs logLevel to tell audit from info and debug from verbose. The level is read from LEVEL, so colorize() cannot stop the mapping. Adds an opt-in maxAttributeLength cap that ends in [Truncated].
CallbackTransport onlyLevels forwards exact levels: level is a threshold, so level: 'audit' also forwards error and warn. closeTimeoutMs bounds how long close() waits for dispatches still running, and reports them through logError.
createErrorSerializer New. omit(key, error) leaves properties out of every error, and dedupe writes a shared object once. serializeError is this factory with no options.
Lint Library source cannot import @opentelemetry/*, so the optional peer cannot become a real dependency.
README A three-step quick start: instrument the app, create the logger, and send audit records to their own Log Analytics table. It replaces the environment-driven configuration example, the hand-written audit transport and the OpenTelemetry setup section. New sections: "What audit means", "Record size and privacy" with an AI SDK example, and "Migrating from v2".

The library has no knowledge of any consumer's libraries. The AI SDK setup is a README example built from redactPaths, createErrorSerializer and maxAttributeLength, and the real-transport spec runs it.

Suggested review order

  1. src/index.ts: the otel option, shapeOtelTransports, and the check for removed options.
  2. src/otel-attributes-format.ts and src/audit-level.ts: the record shape and the audit mapping.
  3. src/callback-transport.ts and src/serialize-error.ts: the new options.
  4. src/winston-instrumentation.spec.ts and src/otel-transport.spec.ts: the end-to-end proof.
  5. README, rendered: the quick start, then OpenTelemetry and Migrating from v2.

Tests

  • winston-instrumentation.spec.ts runs the real WinstonInstrumentation in its own process, loaded before winston as an app's tracing module is. It checks the exported records and that a sibling transport stays nested.
  • otel-transport.spec.ts drives the real transport on 0.29 and the latest line into an in-memory logs SDK. It also pins one upstream behaviour: with no err or error on a record, the transport takes a metadata object with a name as the exception. The README documents it.

Release

  • v2.x is cut from 2.3.0 for fixes only. Its publish workflow runs by hand and publishes under the latest-2 dist-tag, so a 2.x release never moves latest. PR CI also runs for PRs into v2.x.
  • After release, each consumer moves to the README quick start:
    • graphql-template-esm: otel: true replaces flatten and mapAuditLevelForOtel, and the logging.flatten config key goes. Its audit transport can use onlyLevels and closeTimeoutMs in place of its own level check and shutdown timeout.
    • makerx-reports: remove its own transport, disableLogSending: true and mapAuditLevelForOtel, and set otel: true.
    • MakerBOS: replace its own OpenTelemetryTransportV3 with WinstonInstrumentation, loaded before anything imports winston as in step 1 of the quick start, and set otel: true. In the same change, move the transport to 0.29+ and the SDK to 0.219+.

Verification

  • npm run build: lint, types, rollup, attw and the CJS smoke test pass.
  • npm test: 240 tests pass.
  • npm run audit passes.
  • The quick-start snippets type-check against the source.
  • The published package contains no test fixture.
  • npm pack of dist installed in a scratch project: refused beside transport 0.27, installs beside 0.34.

…pacts retries and caps strings

Adds mapAuditLevel, redactKeys, redactedValue, compactErrors and
maxAttributeLength options, all on by default. Adds compactErrorSerializer
and defaultOtelRedactKeys exports.
… otel

createLogger throws when a removed option is passed. The level: 'audit'
guard now applies only to the logger-level otel placement.
…l peer, release 3.0.0

The real-transport spec runs on 0.29 and the latest line under npm aliases.
Lint forbids OpenTelemetry imports in library source. PR CI also runs for
the v2.x branch.
otelAttributesFormat no longer redacts requestBodyValues, compacts retries
or caps strings by default: redactKeys, redactedValue, compactErrors,
defaultOtelRedactKeys and compactErrorSerializer are removed.

- createErrorSerializer({ omit, dedupe }) leaves properties out of every
  error, or writes a shared object once. serializeError is its default.
- maxAttributeLength is off by default.
- The README shows the AI SDK setup as an example built from redactPaths,
  createErrorSerializer and maxAttributeLength, and the transport spec
  runs that example on 0.29 and the latest line.

@cuzzlor cuzzlor left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dual review summary

Findings: 8 (blocker 0, major 1, minor 5, nit 2)

The change is coherent. One otel option replaces three, createErrorSerializer is a small extension of the existing walk, and the real-transport spec runs on both 0.29 and 0.34. The main risk is an audit-severity regression: the audit mapping now runs after loggerOptions.format but compares the mutable info.level, so a format such as format.colorize() stops audit records from being mapped. The upgrade docs also miss that otelAttributesFormat now maps audit by default. The reviewers disagreed on overall quality: Claude found no code defect, and GPT found the audit regression. On omit being called for plain error-shaped objects, they agreed on the diagnosis but disagreed on the fix.

Findings

  • major: Audit mapping fails after a format that changes info.level (gpt-only)
  • minor: omit is called for plain objects that look like errors (disputed)
  • minor: Migration guide does not mention the new audit-mapping default (claude-only)
  • minor: The claim that two api-logs copies drop every line is wrong in most cases (claude-only)
  • minor: Surrogate-pair test does not exercise the surrogate guard (claude-only)
  • minor: A maxAttributeLength below 11 is exceeded (gpt-only)
  • nit: Warning about mapAuditLevel: false applies only when logSeverity is set (claude-only)
  • nit: jsonStringifyValuesFormat JSDoc still recommends it for the OpenTelemetry transport (claude-only)

Findings not anchored to the diff

No line comment could be attached to these findings.

  • nit: jsonStringifyValuesFormat JSDoc still recommends it for the OpenTelemetry transport
    • Impact: The JSDoc at src/json-stringify-values-format.ts:5-7 still names "OTEL + Azure Log Analytics" as the use case, but 3.0 supports only transport 0.29 and later, where this format turns an error into one exception.message string (src/otel-transport.spec.ts:193), and IDE users see this text rather than the updated README row at README.md:376.
    • Recommendation: Match the README: say the format is for sinks other than the OpenTelemetry transport, and that the transport needs otelAttributesFormat.

Generated by dual-review — Claude + litellm/azure/gpt-5.6-sol.

Comment thread src/audit-level.ts Outdated
Comment thread src/serialize-error.ts Outdated
Comment thread README.md
Comment thread README.md Outdated
Comment thread src/otel-attributes-format.spec.ts Outdated
Comment thread src/otel-attributes-format.ts Outdated
Comment thread README.md Outdated
- Read the audit level from LEVEL, so a format such as colorize() before
  otel no longer stops audit records being mapped. Keep an existing logLevel
  so a second pass stays idempotent.
- Cap a string to as much of [Truncated] as fits when maxAttributeLength is
  shorter than the marker, and cut nothing for a negative or NaN limit.
- Make the surrogate-pair test cut inside a pair.
- Document that omit also runs for plain objects with a string name,
  message and stack.
- README: cover the audit-mapping default in Migrating from v2, state when
  two api-logs copies drop lines, and make the severityMapping warning
  unconditional. Point jsonStringifyValuesFormat users at
  otelAttributesFormat for the OpenTelemetry transport.
@cuzzlor

cuzzlor commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator Author

The unanchored nit from the review, the jsonStringifyValuesFormat JSDoc that still recommended it for OpenTelemetry, is fixed in 08cf9f7. The JSDoc now says not to use it for @opentelemetry/winston-transport and points at otelAttributesFormat, matching the README. All eight review findings are addressed.

@cuzzlor
cuzzlor marked this pull request as ready for review October 16, 2026 08:37
@cuzzlor
cuzzlor requested review from mderriey and robdmoore October 16, 2026 08:38
createLogger({ otel: true }) now gives otelAttributesFormat to each
OpenTelemetryTransportV3 on the logger that has no format of its own: the
one WinstonInstrumentation adds, and any passed in transports. The console
and other transports keep nested values and level 'audit'.

- The audit mapping runs after the transport's level filter, so the
  level: 'audit' guard is removed.
- A spec runs the real WinstonInstrumentation before winston loads, as an
  app's tracing module does, and checks the exported records and a sibling
  transport.
- The README leads with one setup: WinstonInstrumentation beside the other
  instrumentations, and otel: true. The per-transport placement and its
  examples are gone, and Migrating from v2 maps every 2.x setup onto it.
@cuzzlor cuzzlor changed the title v3: one otel option, transport 0.29+, generic error shaping v3: one OpenTelemetry setup, transport 0.29+, generic error shaping Oct 17, 2026
Azure Monitor keeps the severity number but not severityText, so the
alternative lost the audit/info distinction that logLevel keeps.
otelAttributesFormat always writes logLevel and exports audit at info
severity. The rewrite only touches a record at the audit level, so a
logger without one has nothing rewritten. Turning it off only lost
logLevel, which App Insights needs to tell audit from info and debug from
verbose, or left audit records with no severity.
…kTransport

- README: a three-step quick start (instrument, create the logger, send
  audit records to their own table) replaces the environment-driven
  configuration example, the hand-written audit transport and the
  OpenTelemetry setup section.
- CallbackTransport: onlyLevels forwards exact levels, because level is a
  threshold and level: 'audit' also forwards error and warn.
  closeTimeoutMs bounds how long close() waits for dispatches still
  running, and reports them through logError.
- Quick start: name @opentelemetry/instrumentation for the ESM hook, say
  what a CommonJS app does instead, and tighten the audit notes.
- Replace the claim that Azure Monitor ignores a record with no severity:
  the collector files it as Information and the JS exporter sends none.
- Describe otel as shaping every OpenTelemetry transport on the logger, and
  stop presenting otelAttributesFormat on a transport as the first choice.
- Remove a repeated sentence from Log levels, and stale wording in the
  formats table, the ESLint comment and a test title.
@cuzzlor
cuzzlor merged commit c54a8b8 into main Oct 17, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

v3 roadmap

2 participants